package cn.yours.elfinder.core.impl.minio;

import cn.yours.elfinder.ElFinderConstants;
import cn.yours.elfinder.core.Target;
import cn.yours.elfinder.core.Volume;
import cn.yours.elfinder.core.VolumeBuilder;
import cn.yours.elfinder.param.MinioDriverConfig;
import cn.yours.elfinder.param.Node;
import io.minio.messages.Item;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static cn.yours.elfinder.service.VolumeSources.MINIO;

/**
 * Aliyun OSS Volume Implementation.
 *
 * @author Van
 */
public class MinioFileSystemVolume implements Volume {

    private static final Logger logger = LoggerFactory.getLogger(MinioFileSystemVolume.class);

    private final String alias;
    private final String source;
    private final String bucketName;
    private final MinioDriverConfig minioDriverConfig;
    private final Target rootTarget;
    private final MinioService minioService;

    private MinioFileSystemVolume(Builder builder, Node nodeConfig) {

        this.minioDriverConfig = nodeConfig.getMinioDriverConfig();
        if(minioDriverConfig == null) {
            throw new RuntimeException("Please config your aliyun driver");
        }

        this.minioService = new MinioService(minioDriverConfig.getEndpoint(),minioDriverConfig.getAccessKeyId(),minioDriverConfig.getAccessKeySecret());
        this.alias = builder.alias;
        this.bucketName = builder.bucketName;
        this.rootTarget = new MinioFileSystemTarget(this, this.minioService.getRootItem(bucketName));
        this.source = MINIO.name();

        checkBucket();
    }

    public MinioService getminioService() {
        return this.minioService;
    }

    public String getBucketName() {
        return this.bucketName;
    }

    private void checkBucket() {
        if(!this.minioService.isBucketExists(bucketName)){
            throw new RuntimeException("Unable to create root dir folder");
        }
    }

    @Override
    public String getAlias() {
        return alias;
    }

    @Override
    public String getSource() {
        return source;
    }

    @Override
    public void createFile(Target target) throws IOException {
        this.minioService.createFile(getBucketName(), getPath(target));
    }

    @Override
    public void createFolder(Target target) throws IOException {
        String path = getPath(target);
        path = this.minioService.fixOssFolderName(path);
        this.minioService.createFile(getBucketName(), path);
    }

    @Override
    public void deleteFile(Target target) throws IOException {
        ((MinioFileSystemTarget)target).delete();
    }

    @Override
    public void deleteFolder(Target target) throws IOException {
        ((MinioFileSystemTarget)target).delete();
    }

    @Override
    public boolean exists(Target target) {
        boolean exists = ((MinioFileSystemTarget)target).exists();
        return exists;
    }

    @Override
    public Target fromPath(String ossKey) {
        if(ossKey.startsWith(ElFinderConstants.ELFINDER_PARAMETER_FILE_SEPARATOR)){
            ossKey = ossKey.substring(1);
        }
        if(ossKey.length() == 0){
            return this.rootTarget;
        }
        Item Item = this.minioService.getOssObjectSummary(bucketName, ossKey);
        MinioFileSystemTarget target = new MinioFileSystemTarget(this, Item);
        return target;
    }

    public Target fromOSSObjectSummary(Item Item) {
        MinioFileSystemTarget target = new MinioFileSystemTarget(this, Item);
        return target;
    }

    @Override
    public long getLastModified(Target target) throws IOException {
        return ((MinioFileSystemTarget)target).getLastModified();
    }

    @Override
    public String getMimeType(Target target) throws IOException {
        return ((MinioFileSystemTarget)target).getMimeType();
    }

    @Override
    public String getName(Target target) {
        return ((MinioFileSystemTarget)target).getName();
    }

    @Override
    public String getCsscls(Target target) {
        return ((MinioFileSystemTarget)target).getCsscls(target);
    }

    @Override
    public String getExternalUrl(Target target) {
        String protocol = "https://";
        if(minioDriverConfig.getBindedDomain() != null){
            return protocol + minioDriverConfig.getBindedDomain() + ElFinderConstants.ELFINDER_PARAMETER_FILE_SEPARATOR + ((MinioFileSystemTarget)target).getKey();
        } else {
            return protocol + bucketName + "." + minioDriverConfig.getEndpoint() + ElFinderConstants.ELFINDER_PARAMETER_FILE_SEPARATOR + ((MinioFileSystemTarget)target).getKey();
        }
    }

    @Override
    public String[] getDisabledCmds(Target target) {
        return ((MinioFileSystemTarget)target).getDisabledCmds(target);
    }

    @Override
    public Target getParent(Target target) {
        String parentPath = ((MinioFileSystemTarget)target).getParentPath();
        Target parent = fromPath(parentPath);
        return parent;
    }

    @Override
    public String getPath(Target target) throws IOException {
        return ((MinioFileSystemTarget)target).getKey();
    }

    @Override
    public Target getRoot() {
        return rootTarget;
    }

    @Override
    public long getSize(Target target) throws IOException {
        return ((MinioFileSystemTarget)target).getSize();
    }

    @Override
    public boolean isFolder(Target target) {
        try {
            return this.minioService.isFolder(getPath(target));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean isRoot(Target target) {
        return ((MinioFileSystemTarget)target).isRoot();
    }

    @Override
    public boolean hasChildFolder(Target target) throws IOException {
        return this.minioService.hasChildFolder(bucketName, getPath(target));
    }

    @Override
    public Target[] listChildren(Target target) throws IOException {

        List<Item> childrenResultList = this.minioService.listChildren(bucketName, getPath(target));
        List<Target> targets = new ArrayList<>(childrenResultList.size());
        for (Item Item : childrenResultList) {
            targets.add(fromPath(Item.objectName()));
        }
        return targets.toArray(new Target[targets.size()]);
    }

    @Override
    public InputStream openInputStream(Target target) throws IOException {
        return this.minioService.openInputStream(bucketName, getPath(target));
    }

    @Override
    public void rename(Target origin, Target destination) throws IOException {
        //AliyunOssHelper.rename(getPath(origin), getPath(destination));
        throw new UnsupportedOperationException();
    }

    @Override
    public List<Target> search(String target) throws IOException {
        List<Target> targets = new ArrayList<>(0);
        return Collections.unmodifiableList(targets);
    }

    @Override
    public void createAndCopy(Target src, Target dst) throws IOException {
        if (src.getVolume().isFolder(src)) {
            createAndCopyFolder(src, dst);
        } else {
            createAndCopyFile(src, dst);
        }
    }

    private void createAndCopyFile(Target src, Target dst) throws IOException {
        InputStream is = src.getVolume().openInputStream(src);
        putFile(dst, is);
        is.close();
    }

    private void createAndCopyFolder(Target src, Target dst) throws IOException {
        dst.getVolume().createFolder(dst);

        String dstPath = dst.getVolume().getPath(dst);
        dstPath = this.minioService.fixOssFolderName(dstPath);
        for (Target c : src.getVolume().listChildren(src)) {
            createAndCopy(c, dst.getVolume().fromPath(dstPath + c.getVolume().getName(c)));
        }
    }

    @Override
    public void putFile(Target target, String content, String encoding) {
        try {
            deleteFile(target);
            this.minioService.createFile(bucketName, getPath(target), content.getBytes(encoding));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void putFile(Target target, InputStream inputStream) {
        try {
            deleteFile(target);
            this.minioService.createFile(bucketName, getPath(target), inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Gets a Builder for creating a new AliyunOssFileSystemVolume instance.
     *
     * @return a new Builder for AliyunOssFileSystemVolume.
     */
    public static Builder builder(String alias, String bucketName, Node nodeConfig) {
        return new MinioFileSystemVolume.Builder(alias, bucketName, nodeConfig);
    }

    public static class Builder implements VolumeBuilder<MinioFileSystemVolume> {
        // required fields
        private final String alias;
        private final String bucketName;
        private final Node nodeConfig;

        public Builder(String alias, String bucketName, Node nodeConfig) {
            this.alias = alias;
            this.bucketName = bucketName;
            this.nodeConfig = nodeConfig;
        }

        @Override
        public MinioFileSystemVolume build() {
            return new MinioFileSystemVolume(this, nodeConfig);
        }
    }

}